#include <dspSimulator/instruction/shl.h>


bool SHL::checkCondition(std::shared_ptr<DspMachine> dspMachine) {
    if (isCompactInstruction) {
        return true;
    } else {
        return checkStandardCondition(dspMachine);
    }
}

void SHL::loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                            std::shared_ptr<llvm::IRBuilder<>> irBuilder) {
    llvm::FunctionType *funcType = llvm::FunctionType::get(irBuilder->getVoidTy(), false);
    // --------------------------------- SHL (.unit) xsint, uint, dst---------------------------------------------
    {
        llvm::Function *shl = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "SHLKind1",
                                                     module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", shl);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2 = irBuilder->CreateLoad(src2RegPtr);

        auto cst6 = getCstN(module, irBuilder, 6);
        auto ucst6 = irBuilder->CreateZExt(cst6, irBuilder->getInt32Ty());

        auto res = irBuilder->CreateShl(src2, ucst6);
        irBuilder->CreateStore(res, dstRegPtr);

        irBuilder->CreateRetVoid();
    }

    // --------------------------------- SHL (.unit) slong,uint,slong ---------------------------------------------
    {
        llvm::Function *shlS = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "SHLKind2",
                                                      module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", shlS);
        irBuilder->SetInsertPoint(entryBB);

        auto dstLoRegPtr = getDstLoRegPtr(module, irBuilder);
        auto dstHiRegPtr = getDstHiRegPtr(module, irBuilder);

        auto src2 = loadLongSrc2ByIR(module, irBuilder);

        auto cst6 = getCstN(module, irBuilder, 6);
        auto ucst6 = irBuilder->CreateZExt(cst6, irBuilder->getIntNTy(40));

        auto res = irBuilder->CreateShl(src2, ucst6);

        storeLongIntoDst(irBuilder, res, dstLoRegPtr, dstHiRegPtr);
        irBuilder->CreateRetVoid();
    }

    // --------------------------------- SHL (.unit) xuint,uint,ulong---------------------------------------------
    {
        llvm::Function *shlS = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "SHLKind3",
                                                      module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", shlS);
        irBuilder->SetInsertPoint(entryBB);

        auto dstLoRegPtr = getDstLoRegPtr(module, irBuilder);
        auto dstHiRegPtr = getDstHiRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);
        auto src2ZExt = irBuilder->CreateZExt(src2Reg, irBuilder->getIntNTy(40));

        auto cst6 = getCstN(module, irBuilder, 6);
        auto ucst6 = irBuilder->CreateZExt(cst6, irBuilder->getIntNTy(40));

        auto res = irBuilder->CreateShl(src2ZExt, ucst6);
        storeLongIntoDst(irBuilder, res, dstLoRegPtr, dstHiRegPtr);

        irBuilder->CreateRetVoid();
    }

    // --------------------------------- SHL (.unit) xsint,ucst5,sint--------------------------------------------
    {
        llvm::Function *shlS = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "SHLKind4",
                                                      module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", shlS);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        auto cst5 = getCstN(module, irBuilder, 5);
        auto ucst5 = irBuilder->CreateZExt(cst5, irBuilder->getInt32Ty());

        auto res = irBuilder->CreateShl(src2Reg, ucst5);
        irBuilder->CreateStore(res, dstRegPtr);

        irBuilder->CreateRetVoid();
    }

    // --------------------------------- SHL (.unit) slong,ucst5,slong ---------------------------------------------
    {
        llvm::Function *shlS = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "SHLKind5",
                                                      module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", shlS);
        irBuilder->SetInsertPoint(entryBB);

        auto dstLoRegPtr = getDstLoRegPtr(module, irBuilder);
        auto dstHiRegPtr = getDstHiRegPtr(module, irBuilder);

        auto src2 = loadLongSrc2ByIR(module, irBuilder);

        auto cst5 = getCstN(module, irBuilder, 5);
        auto ucst5 = irBuilder->CreateZExt(cst5, irBuilder->getIntNTy(40));

        auto res = irBuilder->CreateShl(src2, ucst5);

        storeLongIntoDst(irBuilder, res, dstLoRegPtr, dstHiRegPtr);
        irBuilder->CreateRetVoid();
    }

    // --------------------------------- SHL (.unit) xuint,ucst5,slong---------------------------------------------
    {
        llvm::Function *shlS = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "SHLKind6",
                                                      module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", shlS);
        irBuilder->SetInsertPoint(entryBB);

        auto dstLoRegPtr = getDstLoRegPtr(module, irBuilder);
        auto dstHiRegPtr = getDstHiRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);
        auto src2ZExt = irBuilder->CreateZExt(src2Reg, irBuilder->getIntNTy(40));

        auto cst5 = getCstN(module, irBuilder, 5);
        auto ucst5 = irBuilder->CreateZExt(cst5, irBuilder->getIntNTy(40));

        auto res = irBuilder->CreateShl(src2ZExt, ucst5);
        storeLongIntoDst(irBuilder, res, dstLoRegPtr, dstHiRegPtr);

        irBuilder->CreateRetVoid();
    }
}

std::shared_ptr<Instruction> SHL::decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) {
    std::shared_ptr<SHL> shl;
    auto dspInstInfo = std::dynamic_pointer_cast<DspInstInfo>(instInfo);
    bool isStandardInst = dspInstInfo->isStandardInstruction();
    if (isStandardInst) {
        auto data = vectorToBitSet<32>(dspInstInfo->getBits());
        if (checkBits<10>(data, 2, 0b1100111000) && existStandardCondition(data)) {
            shl = std::make_shared<SHL>(dspInstInfo->getAddress(), baseCycle);
            shl->creg_z = extractBits<4>(data, 28);
            shl->s = data[1];
            shl->x = data[12];
            shl->src1 = extractBits<5>(data, 13).to_ulong();
            shl->src2 = extractBits<5>(data, 18).to_ulong();
            shl->dst = extractBits<5>(data, 23).to_ulong();
            shl->p = data[0];
            if (shl->s == 0) {
                shl->funcUnit = FuncUnit::S1;
            } else {
                shl->funcUnit = FuncUnit::S2;
            }
            shl->instFormat = InstFormat::Kind1;
        } else if (checkBits<10>(data, 2, 0b1100011000) && existStandardCondition(data)) {
            shl = std::make_shared<SHL>(dspInstInfo->getAddress(), baseCycle);
            shl->creg_z = extractBits<4>(data, 28);
            shl->s = data[1];
            shl->x = data[12];
            shl->src1 = extractBits<5>(data, 13).to_ulong();
            shl->src2 = extractBits<5>(data, 18).to_ulong();
            shl->dst = extractBits<5>(data, 23).to_ulong();
            shl->p = data[0];
            if (shl->s == 0) {
                shl->funcUnit = FuncUnit::S1;
            } else {
                shl->funcUnit = FuncUnit::S2;
            }
            shl->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b0100111000) && existStandardCondition(data)) {
            shl = std::make_shared<SHL>(dspInstInfo->getAddress(), baseCycle);
            shl->creg_z = extractBits<4>(data, 28);
            shl->s = data[1];
            shl->x = data[12];
            shl->src1 = extractBits<5>(data, 13).to_ulong();
            shl->src2 = extractBits<5>(data, 18).to_ulong();
            shl->dst = extractBits<5>(data, 23).to_ulong();
            shl->p = data[0];
            if (shl->s == 0) {
                shl->funcUnit = FuncUnit::S1;
            } else {
                shl->funcUnit = FuncUnit::S2;
            }
            shl->instFormat = InstFormat::Kind3;
        } else if (checkBits<10>(data, 2, 0b1100101000) && existStandardCondition(data)) {
            shl = std::make_shared<SHL>(dspInstInfo->getAddress(), baseCycle);
            shl->creg_z = extractBits<4>(data, 28);
            shl->s = data[1];
            shl->x = data[12];
            shl->cst = extractBits<5>(data, 13).to_ulong();
            shl->src2 = extractBits<5>(data, 18).to_ulong();
            shl->dst = extractBits<5>(data, 23).to_ulong();
            shl->p = data[0];
            if (shl->s == 0) {
                shl->funcUnit = FuncUnit::S1;
            } else {
                shl->funcUnit = FuncUnit::S2;
            }
            shl->instFormat = InstFormat::Kind4;
        } else if (checkBits<10>(data, 2, 0b1100001000) && existStandardCondition(data)) {
            shl = std::make_shared<SHL>(dspInstInfo->getAddress(), baseCycle);
            shl->creg_z = extractBits<4>(data, 28);
            shl->dst = extractBits<5>(data, 23).to_ulong();
            shl->src2 = extractBits<5>(data, 18).to_ulong();
            shl->cst = extractBits<5>(data, 13).to_ulong();
            shl->x = data[12];
            shl->s = data[1];
            shl->p = data[0];
            if (shl->s == 0) {
                shl->funcUnit = FuncUnit::S1;
            } else {
                shl->funcUnit = FuncUnit::S2;
            }
            shl->instFormat = InstFormat::Kind5;
        } else if (checkBits<10>(data, 2, 0b0100101000) && existStandardCondition(data)) {
            shl = std::make_shared<SHL>(dspInstInfo->getAddress(), baseCycle);
            shl->creg_z = extractBits<4>(data, 28);
            shl->dst = extractBits<5>(data, 23).to_ulong();
            shl->src2 = extractBits<5>(data, 18).to_ulong();
            shl->cst = extractBits<5>(data, 13).to_ulong();
            shl->x = data[12];
            shl->s = data[1];
            shl->p = data[0];
            if (shl->s == 0) {
                shl->funcUnit = FuncUnit::S1;
            } else {
                shl->funcUnit = FuncUnit::S2;
            }
            shl->instFormat = InstFormat::Kind6;
        }
    } else {
        auto data = vectorToBitSet<16>(dspInstInfo->getBits());
        if (checkBits<3>(data, 10, 0b001) && checkBits<6>(data, 1, 0b110001)) {
            shl = std::make_shared<SHL>(dspInstInfo->getAddress(), baseCycle);
            shl->isCompactInstruction = true;
            shl->compactInstKind = "S2sh";
            shl->src1 = extractBits<3>(data, 13).to_ulong();
            shl->src2 = shl->dst = extractBits<3>(data, 7).to_ulong();
            shl->s = data[0];
            if (shl->s == 0) {
                shl->funcUnit = FuncUnit::S1;
            } else {
                shl->funcUnit = FuncUnit::S2;
            }
            shl->DSZ = dspInstInfo->getDSZ();
            shl->RS = dspInstInfo->getRS();
            shl->SAT = dspInstInfo->getSAT();
            shl->PROT = dspInstInfo->getPROT();
            shl->BR = dspInstInfo->getBR();
            shl->p = dspInstInfo->getP();
            shl->fixUpRegOnCompactInstruction();
            shl->instFormat = InstFormat::Kind1;
        }
    }
    return shl;
}

void SHL::executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) {

}

std::string SHL::toString() const {
    std::string res;
    if (!isCompactInstruction) {
        res += getStandardConditionString() + " ";
    }
    res += m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " ";
    switch (instFormat) {
        case InstFormat::Kind1:
            res += getSrc2Reg32Name() + "," + getSrc1Reg32Name() + "," + getDstReg32Name();
            break;
        case InstFormat::Kind2:
            res += getSrc2Reg64Name() + "," + getSrc1Reg32Name() + "," + getDstReg64Name();
            break;
        case InstFormat::Kind3:
            res += getSrc2Reg32Name() + "," + getSrc1Reg32Name() + "," + getDstReg64Name();
            break;
        case InstFormat::Kind4:
            res += getSrc2Reg32Name() + "," + std::to_string(cst) + "," + getDstReg32Name();
            break;
        case InstFormat::Kind5:
            res += getSrc2Reg64Name() + "," + std::to_string(cst) + "," + getDstReg64Name();
            break;
        case InstFormat::Kind6:
            res += getSrc2Reg32Name() + "," + std::to_string(cst) + "," + getDstReg64Name();
            break;
        default:
            break;
    }
    return res;
}

u32 SHL::computeSHLNumBySrc1(std::shared_ptr<DspMachine> dspMachine) {
    u32 src1Reg = *src1LoRegPtr;
    auto src1RegLSB6 = src1Reg & 0x0000003f;
    return src1RegLSB6 > 39 ? 40 : src1RegLSB6;
}

void SHL::updateContextBeforeExecute(std::shared_ptr<TargetMachine> targetMachine, std::shared_ptr<LLVMJit> llvmJit) {
    std::shared_ptr<DspMachine> dspMachine = std::dynamic_pointer_cast<DspMachine>(targetMachine);
    // 指令执行之前，设置pce1寄存器，使pce1寄存器的值为当前执行的指令所在的取指包的第一条指令的地址
    auto pce1Reg = targetMachine->getRegisters()->getReg32ByName("PCE1");
    pce1Reg->updateData(getInstructionAddress() - getInstructionAddress() % 0x20);

    updateRegAndCstPtr(dspMachine->getSaveRegs(), dspMachine->getRegisters());
    if (instFormat == InstFormat::Kind1 || instFormat == InstFormat::Kind2 || instFormat == InstFormat::Kind3)
        cst = computeSHLNumBySrc1(dspMachine);
}

std::shared_ptr<DspInstruction> SHL::clone() {
    return std::make_shared<SHL>(*this);
}
